package local.xinshi.netmgr.pkg;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;

import javax.annotation.PostConstruct;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;

@Service
public class SshService {
    private static final Logger logger = LoggerFactory.getLogger(SshService.class);
    private Connection conn;
    private Session session;
    private BufferedReader reader;
    private OutputStream writer;
    private ScheduledFuture<?> heartbeatFuture;
    private ScheduledFuture<?> reconnFuture;
    private List<ScheduledFuture<?>> autoDenyList; // 存放自动关闭所有房间网络的计划任务
    private Map<String, ScheduledFuture<?>> timeList; // 存放Web添加的计划任务
    private Map<String, String> scheduledTasks; // Web计划任务的描述

    /**
     * 移除指定的计划任务
     * 
     * @param key
     */
    public String delTask(String key) {
        ScheduledFuture<?> future = this.timeList.remove(key);
        if (future != null)
            future.cancel(true);
        return Instant.ofEpochMilli(Long.parseLong(key))
        .atZone(ZoneId.systemDefault()).toLocalTime().toString().substring(0,8)
         + this.scheduledTasks.remove(key);
    }

    public Map<String, String> getScheduledTasks() {
        return scheduledTasks;
    }

    @Autowired
    ObjectMapper mapper;

    @Autowired
    ThreadPoolTaskScheduler scheduler;

    @Autowired
    SqlService sqlService;

    public String permitAcl;
    public String denyAcl;
    public boolean enableAuto;
    public String portGroup;
    public Map<String, Boolean> states; // 房间对流量状态的映射：room:state
    public Map<String, String> roomMap; // 房间对交换机接口的映射：room:if
    public Map<String, String> ifMap; // 交换机接口对房间的映射：if:room

    /**
     * 启动时立即连接SSH，连接失败时添加计划任务：每5分钟重连一次
     */
    @PostConstruct
    private void init() {
        scheduledTasks = new HashMap<>();
        timeList = new HashMap<>();
        states = new HashMap<>();
        ifMap = new HashMap<>();
        scheduler.setPoolSize(15);
        try {
            this.connect();
        } catch (Exception e) {
            this.reconnect();
        }
    }

    /**
     * 连接SSH服务器，连接成功后如果有重新连接的计划任务则取消，同时查询受控接口的状态并启动心跳和自动断网计划任务(如果已启用)
     * 
     * @throws Exception
     */
    private void connect() throws Exception {
        this.close();
        ifMap.clear();
        states.clear();
        permitAcl = sqlService.getVal("PermitAcl");
        denyAcl = sqlService.getVal("DenyAcl");
        enableAuto = !sqlService.getVal("EnableAuto").equals("0");
        portGroup = sqlService.getVal("PortGroup");
        roomMap = sqlService.getRoom();
        roomMap.forEach((k, v) -> {
            ifMap.put(v, k);
        });
        String host = sqlService.getVal("SshHost");
        String sshUser = sqlService.getVal("SshUser");
        String pwd = sqlService.getVal("SshPwd");
        conn = new Connection(host);
        conn.connect();
        if (!conn.authenticateWithPassword(sshUser, pwd))
            throw new Exception();
        session = conn.openSession();
        session.requestPTY("vty");
        session.startShell();
        writer = session.getStdin();
        reader = new BufferedReader(new InputStreamReader(session.getStdout()));
        writer.write("sys\n".getBytes());
        heartbeatFuture = scheduler.scheduleWithFixedDelay(this::heartbeat, Instant.now().plusSeconds(5),
                Duration.ofMinutes(5));
        getStates();
        if (enableAuto)
            enableAutoDeny();
        if (reconnFuture != null) {
            reconnFuture.cancel(true);
            reconnFuture = null;
        }
    }

    /**
     * 获取各房间的网络状态
     */
    public synchronized void getStates() {
        String line;
        boolean isRoom = false;
        String cuif = null;
        String currentRoom = null;
        List<String> newRooms = new ArrayList<>();
        Map<String, String> notRooms = new HashMap<>();
        try {
            writer.write("display cu interface\n".getBytes());
            writer.flush();
            while (true) {
                line = reader.readLine().trim();
                if (line.contains("----")) {
                    writer.write("q\nuser-interface vty 0 4\nscreen-length 0\nquit\n".getBytes());
                    writer.flush();
                    Thread.sleep(100);
                    getStates();
                    return;
                }
                if (line.endsWith("return"))
                    break;
                if (line.startsWith("interface")) {
                    if (isRoom) {
                        newRooms.add(currentRoom);
                    }
                    cuif = line.substring(line.indexOf(" ") + 1);
                    currentRoom = ifMap.get(cuif);
                    isRoom = currentRoom != null;
                    continue;
                }
                if (line.startsWith("traffic")) {
                    String cuAcl = line.substring(line.lastIndexOf(" ") + 1);
                    if (isRoom) {
                        isRoom = false;
                        states.put(currentRoom, cuAcl.equals(permitAcl));
                    } else if (cuAcl.equals(denyAcl) || cuAcl.equals(permitAcl)) {
                        notRooms.put(cuif, cuAcl.equals(denyAcl) ? denyAcl : permitAcl);
                    }
                }
            }
            newRooms.forEach(room -> {
                toggle(room, false);
            });
            notRooms.forEach((swIf, acl) -> {
                send("interface " + swIf + "\nundo traffic-filter inbound acl name " + acl + "\nquit\n", "quit");
            });
        } catch (Exception e) {
            logger.info("获取状态异常：" + e.getMessage());
            this.reconnect();
        }
    }

    /**
     * 心跳任务
     */
    private void heartbeat() {
        try {
            writer.write("\n".getBytes());
            writer.flush();
            reader.readLine();
        } catch (Exception e) {
            logger.info("心跳异常: " + e.getMessage());
            reconnect();
        }
        logger.info("心跳正常");
    }

    /**
     * 添加重新连接SSH的计划任务
     */
    public synchronized void reconnect() {
        if (reconnFuture != null) {
            reconnFuture.cancel(true);
        }
        try {
            connect();
        } catch (Exception e) {
            reconnFuture = scheduler.scheduleWithFixedDelay(() -> {
                try {
                    connect();
                } catch (Exception ein) {
                    logger.info("SSH连接失败，5分钟后重试");
                }
            }, 300000);
        }
    }

    /**
     * 更改交换机接口组内所有接口的ACL
     * 
     * @param open true:permitAcl false:denyAcl
     * @param sid  不广播websocket的sessionID，如果为null或空字符串则全部广播
     * @return 1 操作成功 0 操作失败
     */
    public synchronized int doAll(boolean open, String sid) {
        String cmds = String.format(
                "port-group %s%ntraffic-filter inbound acl name %s%nundo traffic-filter inbound acl name %s%nquit%n",
                portGroup, open ? permitAcl : denyAcl, open ? denyAcl : permitAcl);
        try {
            writer.write(cmds.getBytes());
            String line;
            while (true) {
                line = reader.readLine().trim();
                if (line.endsWith("quit"))
                    break;
            }
            for (Map.Entry<String,Boolean> entry : states.entrySet()) {
                entry.setValue(open);
            }
            WebSessions.broad(mapper.writeValueAsString(states), sid);
            return 1;
        } catch (Exception e) {
            logger.info("操作失败：" + e.getMessage());
            this.reconnect();
            return 0;
        }
    }

    /**
     * 设定超时开关指定机房的网络并发送websocket广播
     * 
     * @param room  要超时开关网络的房间，传入null则所有房间都添加超时计划
     * @param tm    超时时间(分钟)
     * @param index Web控制页面的room所在行的索引(0开始)
     * @param state 定时开关执行后的状态
     */
    public synchronized void timeToToggle(String room, int tm, int index, boolean state) {
        Instant actionTime = Instant.now().plusSeconds(tm*60);
        String key = actionTime.toEpochMilli() + "";
        scheduledTasks.put(key, (state ? "开启" : "关闭") + (room == null ? "所有房间" : room));
        timeList.put(key, scheduler.schedule(() -> {
            if (room == null) {
                doAll(state, "");
            } else {
                toggle(room, state);
                WebSessions.broad(String.format("{\"index\":%d,\"state\":%b}", index, state), "");
            }
            timeList.remove(key);
            scheduledTasks.remove(key);
        }, actionTime));
    }

    /**
     * 清除超时开关网络的计划
     */
    private void clearTimeList() {
        if (timeList.size() == 0)
            return;
        for (String k : timeList.keySet()) {
            timeList.remove(k).cancel(true);
        }
        scheduledTasks.clear();
    }

    /**
     * 根据设定的时间，添加自动断网的计划任务
     */
    public void enableAutoDeny() {
        if (autoDenyList == null) {
            autoDenyList = new ArrayList<>();
        }
        String[] tms = sqlService.getVal("AutoDeny").split(",");
        for (int i = 0; i < tms.length; i++) {
            int x = tms[i].indexOf(":");
            autoDenyList.add(scheduler.schedule(() -> {
                doAll(false, "");
                clearTimeList();
            }, new CronTrigger(String.format("0 %s %s * * ?", tms[i].substring(x + 1), tms[i].substring(0, x)))));
        }
    }

    /**
     * 取消自动断网的计划任务
     */
    public void disableAutoDeny() {
        if (autoDenyList != null && autoDenyList.size() > 0) {
            autoDenyList.forEach(future -> {
                future.cancel(true);
                future = null;
            });
            autoDenyList.clear();
            autoDenyList = null;
        }
    }

    private void send(String cmds, String end) {
        try {
            writer.write(cmds.getBytes());
            String line;
            while (true) {
                line = reader.readLine().trim();
                if (line.endsWith(end))
                    break;
            }
        } catch (Exception e) {
            logger.info("操作异常：" + e.getMessage());
            this.reconnect();
        }
    }

    /**
     * 开关指定房间的网络状态
     * 
     * @param room    要开关的房间
     * @param cuState 当前状态
     * @return 1 开关正常 0 开关异常
     */
    public synchronized int toggle(String room, boolean cuState) {
        String cmds = "interface " + roomMap.get(room) + "\ntraffic-filter inbound acl name "
                + (cuState ? permitAcl : denyAcl) + "\nundo traffic-filter inbound acl name "
                + (cuState ? denyAcl : permitAcl) + "\nquit\n";
        int i = 1;
        try {
            writer.write(cmds.getBytes());
            String line;
            while (true) {
                line = reader.readLine().trim();
                if (line.endsWith("quit"))
                    break;
            }
            states.put(room, cuState);
        } catch (Exception e) {
            logger.info("操作异常：" + e.getMessage());
            i = 0;
            this.reconnect();
        }
        return i;
    }

    /**
     * 关闭资源
     */
    private void close() {
        if (heartbeatFuture != null) {
            heartbeatFuture.cancel(true);
            heartbeatFuture = null;
        }
        clearTimeList();
        disableAutoDeny();
        try {
            if (reader != null)
                reader.close();
            if (writer != null)
                writer.close();
        } catch (Exception e) {
        }
        if (session != null)
            session.close();
        if (conn != null)
            conn.close();
    }
}